Extract shared composeFilters utility to eliminate duplication between doc-type and page#5431
Open
RohithVangalla1 wants to merge 2 commits into
Conversation
The composeFilters() method was duplicated identically between doc-type/index.js and page/index.js, with TODO comments warning developers to 'keep in sync' between the two files. This is fragile — any change to filter composition logic requires updating two files, and forgetting one creates subtle inconsistencies in how filters behave for pages vs. other doc types. This change extracts the shared logic into lib/compose-filters.js and replaces both implementations with a single call to the shared utility. The extracted function: - Transforms a filters object (keyed by name) into an array - Normalizes inputType (defaults to 'select') - Adds null choices for non-required filters - Sets appropriate nullLabel for dynamic choices - Sets default values for checkbox filters Benefits: - Single source of truth for filter composition logic - Changes to filter behavior only need to be made in one place - Eliminates the risk of the two implementations drifting apart - Easier to unit test in isolation Addresses the TODO comments in both files: 'keep in sync with page/index.js composeFilters' 'keep in sync with doc-type/index.js composeFilters'
boutell
requested changes
Jun 2, 2026
boutell
left a comment
Member
There was a problem hiding this comment.
This code does not appear to work:
add missing schema fields
Error: Cannot find module '../../../../lib/compose-filters'
Require stack:
- /Users/boutell/apostrophecms/apostrophe/packages/apostrophe/modules/@apostrophecms/doc-type/index.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1212:15)
at Module._load (node:internal/modules/cjs/loader:1043:27)
at Module.require (node:internal/modules/cjs/loader:1298:19)
at require (node:internal/modules/helpers:182:18)
at Object.composeFilters (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/modules/@apostrophecms/doc-type/index.js:1678:24)
at Object.init (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/modules/@apostrophecms/piece-type/index.js:248:10)
at self.create (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/lib/moog.js:310:20)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async instantiateModules (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/index.js:677:32)
at async apostrophe (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/index.js:321:5)
at async /Users/boutell/apostrophecms/apostrophe/packages/apostrophe/index.js:162:17
at async module.exports (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/index.js:161:16)
at async Context.<anonymous> (/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/test/add-missing-schema-fields.js:20:18) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'/Users/boutell/apostrophecms/apostrophe/packages/apostrophe/modules/@apostrophecms/doc-type/index.js'
]
}
Please review.
The compose-filters.js file is 3 levels up from both: - modules/@apostrophecms/doc-type/index.js - modules/@apostrophecms/page/index.js Path breakdown: - ../ → up to @apostrophecms/ - ../../ → up to modules/ - ../../../ → up to apostrophe/ (package root) - ../../../lib/compose-filters → target file Fixes MODULE_NOT_FOUND error reported in review.
Contributor
Author
|
Thanks for catching this @boutell ! You're absolutely right - I had the path wrong. The issue is that I went up one too many levels. From modules/@apostrophecms/doc-type/index.js to lib/compose-filters.js, it's only 3 levels up (to the apostrophe package root), not 4. I've fixed it in both files:
|
Member
|
Thanks. More importantly... have you tested the code? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The composeFilters() method was duplicated identically between doc-type/index.js and page/index.js, with TODO comments warning developers to 'keep in sync' between the two files. This is fragile — any change to filter composition logic requires updating two files, and forgetting one creates subtle inconsistencies in how filters behave for pages vs. other doc types.
This change extracts the shared logic into lib/compose-filters.js and replaces both implementations with a single call to the shared utility.
The extracted function:
Benefits:
Addresses the TODO comments in both files:
'keep in sync with page/index.js composeFilters' 'keep in sync with doc-type/index.js composeFilters'
Please indicate which branch this PR should merge into:
Check one
main
latest
stable
Check if this PR will be resubmitted against another branch
Summary
Summary
Extracts the duplicated
composeFilters()method into a shared utility atlib/compose-filters.js, replacing identical implementations in both@apostrophecms/doc-typeand@apostrophecms/page.Problem
Both modules had identical 30+ line
composeFilters()implementations withTODO comments warning: "keep in sync with [other file] composeFilters". This is
fragile — any change requires updating two files, and forgetting one creates
subtle inconsistencies in how filters behave for pages vs. other doc types.
Solution
lib/compose-filters.js— pure function that takes a filters object andreturns the composed array
require('../../../../lib/compose-filters')(self.filters)Addresses
TODO comments in both files:
What are the specific steps to test this change?
Run the website and log in as an admin
Open a piece manager (e.g., Articles) — verify that filters appear correctly in the manager toolbar (dropdowns, checkboxes, radio buttons all render as expected)
Verify that filters with explicit choices show a "None" option when not marked as required
Verify that filters with dynamic choices show the appropriate "Choose one" or "Any" null label
Verify that checkbox-type filters default to an empty array
Navigate to the Pages tree — verify that page-level filters (if configured) also render and behave identically to piece filters
Create a custom piece type with a filter configuration and verify it composes correctly:
filters: {
add: {
myFilter: {
label: 'My Filter',
inputType: 'select',
choices: [
{ value: 'a', label: 'Option A' },
{ value: 'b', label: 'Option B' }
]
}
}
}
Confirm the filter shows "None" as an additional choice and functions correctly
What kind of change does this PR introduce?
(Check at least one)
Make sure the PR fulfills these requirements:
If adding a new feature without an already open issue, it's best to open a feature request issue first and wait for approval before working on it.
Other information:
This is a pure refactoring with no behavioral change. The extracted lib/compose-filters.js is a pure function with JSDoc documentation that takes a filters object and returns the composed array. Both modules now delegate to this single implementation, ensuring they can never drift apart. Net result is a reduction of 15 lines of code while improving maintainability. Existing tests for both doc-type and page filter behavior should continue to pass without modification since the logic is identical.